/*******************************************************************************
 * Copyright(c) 2023 Levetop Semiconductor Co.,Led. All rights reserved.
 * @file     w25n01.c
 * @author   UartTFT Application Team
 * @version  V0.0.1
 * @date     2023-01-01
 * @brief
 ********************************************************************************/

#include "w25N01.h"
#include "w25qxx.h"
#include "type.h"
#include "delay.h"
#include "qspi.h"
#include "dmac_drv.h"
#include "ssi_drv.h"
#include "dma.h"

/********************************************************************************
* Function Name	: LT_W25N01GV_ReadSR
* Description  	: Read the value of the register.
* Input        	: - SRx:Register address
					  - 3 status registers
					  - Protection register 1:    address 0xA0
					  - Configuration register 2: address 0xB0
					  - Status register 3:        address 0xC0  (Read Only)	
* Output       	: None
* Return       	: byte
*********************************************************************************/
uint8_t LT_W25N01GV_ReadSR(uint8_t SRx)
{
	uint8_t byte = 0;
	volatile uint8_t status;
	volatile uint8_t temp;

	SSI_TypeDef *SSIx = SSI1;

	SSIx->SSI_DR = W25N01GV_READ_STATUSREG;
	SSIx->SSI_DR = SRx;
	SSIx->SSI_DR = 0xFF;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	temp = SSIx->SSI_DR; // empty fifo
	temp = SSIx->SSI_DR; 
	byte = SSIx->SSI_DR; // read

	return byte;
}

/********************************************************************************
* Function Name	: LT_W25N01GV_Write_SR
* Description  	: Write the value of the register.
* Input        	: - SRx:Register address
					  - 3 status registers
					  - Protection register 1:    address 0xA0
					  - Configuration register 2: address 0xB0
					  - Status register 3:        address 0xC0  (Read Only)	
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_Write_SR(uint8_t SRx, uint8_t val)
{
	volatile uint8_t status;
	volatile uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	LT_W25N01GV_Write_Enable();

	SSIx->SSI_DR = W25N01GV_WRITE_STATUSREG;
	SSIx->SSI_DR = SRx;
	SSIx->SSI_DR = val;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	LT_W25N01GV_Wait_Busy();
}

/********************************************************************************
* Function Name	: LT_W25N01GV_Wait_Busy
* Description  	: Wait for BUSY bit to clear.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_Wait_Busy(void)
{
	while ((LT_W25N01GV_ReadSR(W25N01GV_SR3) & 0x01) == 0x01)
		;
}

/********************************************************************************
* Function Name	: LT_W25N01GV_Write_Enable
* Description  	: Enable to write.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_Write_Enable(void)
{
	volatile uint8_t status;
	volatile uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	SSIx->SSI_DR = W25N01GV_WRITE_ENABLE;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	LT_W25N01GV_Wait_Busy();
}

/********************************************************************************
* Function Name	: LT_W25N01GV_Write_Disable
* Description  	: Disable to write.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_Write_Disable(void)
{
	volatile uint8_t status;
	volatile uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	SSIx->SSI_DR = W25N01GV_WRITE_DISABLE;
	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}
}

/********************************************************************************
* Function Name	: LT_W25N01GV_ContinuousRead_Mode
* Description  	: Enable continuous read mode.(BUF=0, ECC-E=1)
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_ContinuousRead_Mode(void)
{
	uint8_t statut = 0;
	statut = LT_W25N01GV_ReadSR(W25N01GV_SR2);
	statut &= 0xe7;
	statut |= 0x10;
	LT_W25N01GV_Write_SR(W25N01GV_SR2, statut);
}

/********************************************************************************
* Function Name	: LT_W25N01GV_BufferRead_Mode
* Description  	: Enable buffer read mode.(BUF=1, ECC-E=1)
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_BufferRead_Mode(void)
{
	uint8_t statut = 0;
	
	statut = LT_W25N01GV_ReadSR(W25N01GV_SR2);
	statut |= 0x08;
	statut |= 0x10;
	LT_W25N01GV_Write_SR(W25N01GV_SR2, statut);
}


/********************************************************************************
* Function Name	: LT_W25N01GV_ReadID
* Description  	: Read W25N01GV ID.
* Input        	: None
* Output       	: None
* Return       	: Device_ID
*********************************************************************************/
uint32_t LT_W25N01GV_ReadID(void)
{
	uint32_t Device_ID = 0;

	SSI_TypeDef *SSI_1 = SSI1;

	SSI_1->SSI_DR = W25N01GV_ID;
	SSI_1->SSI_DR = 0xff;

	SSI_1->SSI_DR = 0xff;
	SSI_1->SSI_DR = 0xff;
	SSI_1->SSI_DR = 0xff;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSI_1->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSI_1->SSI_SR & SR_BUSY)
	{
		;
	}

	Device_ID = SSI_1->SSI_DR; // data alignment
	Device_ID = SSI_1->SSI_DR;

	Device_ID = SSI_1->SSI_DR;
	Device_ID = (Device_ID << 8) & 0xFF00;
	Device_ID |= SSI_1->SSI_DR;
	Device_ID = (Device_ID << 8) & 0xFFFF00;
	Device_ID |= SSI_1->SSI_DR;

	return Device_ID;
}

// Init
void W25N01GV_Init(void)
{
	LT_W25N01GV_BufferRead_Mode();
	LT_W25N01GV_PageDataRead(0);   // buffer page 0 data
	DelayUS(50);
	Nand_page = 0;
}

//-------------------------------------------------Erase--------------------------------------------------------
/********************************************************************************
* Function Name	: LT_W25N01GV_BlockErase128KB
* Description  	: Erase a sector (minimum time to erase a sector: 150ms).
* Input        	: - addr:sector address
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_BlockErase128KB(uint32_t addr)
{
	uint8_t temp = 0;
	uint16_t Page = (addr / 2048);           //Address conversion to page

	SSI_TypeDef *SSIx = SSI1;

	LT_W25N01GV_Write_SR(W25N01GV_SR1, 0x00); // close protect
	LT_W25N01GV_Write_Enable();
	LT_W25N01GV_Wait_Busy();

	SSIx->SSI_DR = W25N01GV_128KBLOCK_ERASE;
	SSIx->SSI_DR = 0xff;
	SSIx->SSI_DR = (uint8_t)((Page) >> 8);
	SSIx->SSI_DR = (uint8_t)Page;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	LT_W25N01GV_Wait_Busy();
	LT_W25N01GV_Write_SR(W25N01GV_SR1, 0x7C); // open protect
}

/********************************************************************************
* Function Name	: LT_W25N01GV_FullChipErase
* Description  	: Full Erase.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_FullChipErase(void)
{
	uint16_t k;

	for (k = 0; k < 1024; k++)
		LT_W25N01GV_BlockErase128KB(NandBlockSize * k);
}

/********************************************************************************
* Function Name	: LT_W25N01GV_PageDataRead
* Description  	: Buffer one page of data and wait for the host to read.
* Input        	: - PageNum: Page
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_PageDataRead(uint32_t page)
{
	volatile uint8_t rdByte;
	SSI_TypeDef *SSIx = SSI1;

	SSIx->SSI_DR = W25N01GV_READ_PAGE;
	SSIx->SSI_DR = 0x00;						  // Dummy
	SSIx->SSI_DR = (uint8_t)((page >> 8) & 0xFF); // Page address
	SSIx->SSI_DR = (uint8_t)(page & 0xFF);

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	while (SSIx->SSI_SR & SR_RFNE)
	{
		rdByte = SSIx->SSI_DR; // empty fifo
	}

	LT_W25N01GV_Wait_Busy();
}

/********************************************************************************
* Function Name	: LT_W25N01GV_ReadData
* Description  	: Read data of specified length from the specified address.
* Input        	: - pBuffer: buffer for save data
                  - ReadAddr: Start address of reading data
                  - NumByteToRead: number bytes to read
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_ReadData(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
	volatile uint8_t temp;
	int32_t i = 0, j = 0;
	int32_t txnum;
	uint16_t page, column;
	SSI_TypeDef *SSIx = SSI1;

	page = ReadAddr / NandPageSize;
	column = ReadAddr % NandPageSize;

	LT_W25N01GV_PageDataRead(page);	// copy data to buffer, waiting for read

	txnum = NumByteToRead;	// read
	SSIx->SSI_DR = W25N01GV_READ;
	SSIx->SSI_DR = (uint8_t)(column >> 8) & 0xff;
	SSIx->SSI_DR = (uint8_t)(column >> 0) & 0xff;
	SSIx->SSI_DR = 0xff;	// DUMMY

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while (NumByteToRead > 0)
	{
		if ((SSIx->SSI_SR & SR_TFNF) && (txnum > 0))
		{
			SSIx->SSI_DR = DUMMY_BYTE;
			txnum--;
		}

		if (SSIx->SSI_SR & SR_RFNE)
		{
			if (j < 4)
			{
				temp = SSIx->SSI_DR;
				j++;
			}
			else
			{
				*(pBuffer + i) = SSIx->SSI_DR;
				i++;
				NumByteToRead--;
			}
		}
	}

	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
}

/********************************************************************************
* Function Name	: LT_W25N01GV_ReadData_DMA
* Description  	: Read data of specified length from the specified address.
* Input        	: - pBuffer: buffer for save data
                  - ReadAddr: Start address of reading data
                  - NumByteToRead: number bytes to read
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_ReadData_DMA(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
	uint8_t status;
	int32_t num2;
	uint16_t page, column;

	SSI_TypeDef *SSIx = SSI1;

	page = ReadAddr / NandPageSize;
	column = ReadAddr % NandPageSize;

	LT_W25N01GV_PageDataRead(page); // buffer

	exFlash_dma_control->DMA_CONFIG = 1; // read
	// Rx
	exFlash_dma_channel[exFlash_CHNUM]->DMA_SADDR = (uint32_t)&SSIx->SSI_DR;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_DADDR = (uint32_t)pBuffer;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL = DIEC | SNOCHG | P2M_DMA | DWIDTH_B | SWIDTH_B;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL_HIGH = NumByteToRead + 4; // max lenght 0x0FFF
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG = (HS_SEL_SRC_HARD);
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG_HIGH = exFlash_SRC_RX;
	// enable dma channel
	exFlash_dma_control->DMA_CHEN |= (CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));
	// printf("1m_dma_control->DMA_RAWTFR: 0x%08x\r\n", exFlash_dma_control->DMA_RAWTFR);

	num2 = NumByteToRead;
	// Nand_LT_W25N01GV_DMA_Read_Trig(SSIx);//SSI_STD_DMA_Trig(SSIx, READ_CMD, addr, DMACR_RDMAE);
	SSIx->SSI_DR = W25N01GV_READ;
	SSIx->SSI_DR = column >> 8;
	SSIx->SSI_DR = column;
	SSIx->SSI_DR = 0xFF; // dummy
	SSIx->SSI_DMACR = DMACR_RDMAE;

	while (num2)
	{
		while ((SSIx->SSI_SR & SR_TFNF) && (num2 > 0))
		{
			SSIx->SSI_DR = DUMMY_BYTE;
			num2--;
			while (0)
				;
		}
	}

	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	//	while((exFlash_dma_control->DMA_RAWTFR & CHANNEL_STAT(exFlash_CHNUM)) != CHANNEL_STAT(exFlash_CHNUM));

	while (exFlash_dma_control->DMA_CHEN & CHANNEL_STAT(exFlash_CHNUM))
	{
		;
	}
	exFlash_dma_control->DMA_CHEN &= ~(CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));

	SSIx->SSI_DMACR = 0;
}

//--------------------------------------------------Write---------------------------
/********************************************************************************
* Function Name	: LT_W25N01GV_STD_DMA_Send
* Description  	: On a page, write data of specified length at the specified column address.
* Input        	: - psend: data start address
                  - Column: column
                  - length: number bytes to write(max one page:2048 bytes)
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_STD_DMA_Send(uint8_t *psend, uint16_t Column, uint32_t length)
{
	SSI_TypeDef *SSIx = SSI1;

	exFlash_dma_control->DMA_CONFIG = 1;
	// Tx
	exFlash_dma_channel[exFlash_CHNUM]->DMA_SADDR = (uint32_t)psend;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_DADDR = (uint32_t)&SSIx->SSI_DR;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL = DNOCHG | SIEC | M2P_DMA | DWIDTH_B | SWIDTH_B | SBSIZE_4 | DBSIZE_4;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL_HIGH = length;	// max lenght 0x0FFF
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG = (HS_SEL_DST_HARD);
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG_HIGH = exFlash_DST_TX;
	exFlash_dma_control->DMA_CHEN |= (CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));

	SSIx->SSI_DR = W25N01GV_LOADPRODATA;
	SSIx->SSI_DR = Column >> 8;
	SSIx->SSI_DR = Column;
	SSIx->SSI_DMACR = DMACR_TDMAE;

	while (exFlash_dma_control->DMA_CHEN & CHANNEL_STAT(exFlash_CHNUM))
	{
		;
	}
	exFlash_dma_control->DMA_CHEN &= ~(CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));
	//	exFlash_dma_control->DMA_CONFIG = 0;
	SSIx->SSI_DMACR = 0;
}

/********************************************************************************
* Function Name	: LT_W25N01GV_STD_Write_Page_DMA
* Description  	: On a page, write data of specified length at the specified column address.
* Input        	: - pBuffer: data start address
                  - page: page
                  - Column: column
                  - NumByteToWrite: number bytes to write(max one page:2048 bytes)
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_STD_Write_Page_DMA(uint8_t *pBuffer, uint16_t page, uint16_t column, uint16_t NumByteToWrite)
{
	uint8_t reV;

	SSI_TypeDef *SSIx = SSI1;

	SSI1_STD_Init(SSI1);

	LT_W25N01GV_Write_SR(W25N01GV_SR1, 0x00); // close flash protection function
	LT_W25N01GV_Write_Enable();				  // write enable

	// DMA_REG_Init(DMA2_BASE_ADDR);
	LT_W25N01GV_STD_DMA_Send(pBuffer, column, NumByteToWrite);

	SSI1_STD_Init(SSI1);

	LT_W25N01GV_Wait_Busy();

	SSIx->SSI_DR = W25N01GV_PROEXECUTE;
	SSIx->SSI_DR = 0xff;
	SSIx->SSI_DR = (uint8_t)((page) >> 8);
	SSIx->SSI_DR = (uint8_t)page;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	reV = SSIx->SSI_DR;
	reV = SSIx->SSI_DR;
	reV = SSIx->SSI_DR;
	reV = SSIx->SSI_DR;

	LT_W25N01GV_Wait_Busy();
	LT_W25N01GV_Write_SR(W25N01GV_SR1, 0x7C); // open flash protection function
}

/********************************************************************************
* Function Name	: LT_W25N01GV_STD_Write_NoCheck_DMA
* Description  	: Write SPI FLASH without inspection.
                  You must ensure that all the data written in the address range is 0XFF, otherwise the data written in non-0XFF will fail.
                  With auto-feed function
* Input        	: - pBuffer: data start address
                  - WriteAddr: Address to start writing
                  - NumByteToWrite: number bytes to write(max 0xFFFF bytes)
* Output       	: None
* Return       	: None
*********************************************************************************/
void LT_W25N01GV_STD_Write_NoCheck_DMA(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
	uint16_t page, column, endpage;
	uint16_t pageremain;

	if (NumByteToWrite == 0)
		return;

	page = WriteAddr / 2048;
	column = WriteAddr % 2048;
	pageremain = 2048 - column;

	if (NumByteToWrite <= pageremain)
		pageremain = NumByteToWrite;
	while (1)
	{
		LT_W25N01GV_STD_Write_Page_DMA(pBuffer, page, column, pageremain);
		if (NumByteToWrite == pageremain)
			break;
		else // NumByteToWrite>pageremain
		{
			pBuffer += pageremain;
			WriteAddr += pageremain;
			page++;
			column = 0;

			NumByteToWrite -= pageremain;
			if (NumByteToWrite > 2048)
				pageremain = 2048;
			else
				pageremain = NumByteToWrite;
		}
	}
}

/* NandFlash Dual_Read_Model*/
void NandFlash_Dual_Init(uint8_t command_bits, uint8_t addr_bits, uint8_t dummy_bits, uint32_t num)
{
	SSI_TypeDef *SSIx = SSI1;
#if 1
	//-----------------DUAL MODE-----------------------------------------------------
	//  while(SSIx->SSI_SR&SR_BUSY){;}
	SSIx->SSI_SSIENR = 0x00;
	SSIx->SSI_BAUDR = QUAD_BAUDR;
	SSIx->SSI_CTRLR1 = ((num == 0) ? 0 : (num - 1));
	SSIx->SSI_CTRLR0 = (CTRLR0_SPI_FRF_DUAL | CTRLR0_FRF_MOT | CTRLR0_DFS_VALUE(0x07) | CTRLR0_TMOD_TX_AND_RX); // DUAL model
	//	SSIx->SSI_CTRLR0 |= (CTRLR0_SPI_FRF_DUAL|CTRLR0_FRF_MOT|CTRLR0_DFS_VALUE(0x07)|CTRLR0_TMOD_RX_ONLY);//|
	//						 CTRLR0_SSTE_MASK(0)|CTRLR0_SCPOL_MASK(0)|CTRLR0_SCPH_MASK(0));
	//	SSIx->SSI_SPI_CTRLR0 = 0;
	SSIx->SSI_SPI_CTRLR0 = (CTRLR0_TRANS_TYPE_TT0 | CTRLR0_INST_L_VALUE(command_bits) // CMD_Len: 0=0bit, 1=4bit, 2=8bit, 3=16bit
							| CTRLR0_ADDR_L_VALUE(addr_bits)						  // ADDR_Len: n*4 bit (max_n=15)
							| CTRLR0_WAIT_CYCLES_VALUE(dummy_bits)					  // dummy_clock: max=31
							| CTRLR0_CLK_STRETCH_EN_MASK);							  // Clock extension enable(TX_ONLY necessary)
	//	SSIx->SSI_SPI_CTRLR0 |= CTRLR0_ADDR_L_VALUE(addr_bits);     //addr_len= n*4 bit ( max_n=15)

	SSIx->SSI_TXFTLR = TXFTLR_TFT(0) | TXFTLR_TXFTHR(1);
	SSIx->SSI_RXFTLR = RXFTLR_TFT(7);

	if (QUAD_BAUDR == 0x02 && g_sys_clk >= 80000000) // high speed
	{
		SSIx->SSI_RX_SAMPLE_DELAY = 0x00010001;
	}

	SSIx->SSI_IMR = 0x00;
	SSIx->SSI_DMACR = 0; // dma
	SSIx->SSI_SSIENR = 0x01;
//----------------- END -----------------------------------------------------
#else
//-----------------QUAL MODE-----------------------------------------------------
// none
//----------------- END -----------------------------------------------------
#endif
}

void NandFlash_Read(uint16_t page, uint16_t column, uint8_t *buf, uint32_t num)
{
	int32_t i = 0;

	SSI_TypeDef *SSIx = SSI1;

	//	SSI1_STD_Init(SSI1);
	// DelayUS(1);
	if (page != Nand_page)
	{
		SSI1_STD_Init(SSI1);			// STD
		LT_W25N01GV_PageDataRead(page); // page
		Nand_page = page;
		// DelayUS(50);      //50us
		DelayUS(1);
	}

	NandFlash_Dual_Init(2, 4, 8, num);
	DelayUS(1); // need after Init

	SSIx->SSI_DR = W25N01GV_READ_FAST_DUAL;
	SSIx->SSI_DR = column; // column

	__asm("nop");
	__asm("nop");
	__asm("nop");
	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}

	while (((num > 0) && (SSIx->SSI_SR & SR_BUSY)) || (SSIx->SSI_SR & SR_RFNE))
	//	while( num>0)
	{
		if (SSIx->SSI_SR & SR_RFNE)
		{
			*(buf + i) = SSIx->SSI_DR;
			i++;
			num--;
		}
	}
	//	DelayUS(50);
}

/* NandFlash DMA_Read_Model*/
void NandFlash_DMA_Read(uint16_t page, uint16_t column, uint8_t *buff, uint16_t num)
{
	SSI_TypeDef *SSIx = SSI1;

//-----------------DUAL MODE-----------------------------------------------------
#if 1

	// SSI1_STD_Init(SSI1); //STD
	// DelayUS(1);
	if (page != Nand_page)
	{
		SSI1_STD_Init(SSI1); //STD
		LT_W25N01GV_PageDataRead(page);
		Nand_page = page;
		// DelayUS(50);      //50us
		DelayUS(1);
	}

	// Init
	NandFlash_Dual_Init(2, 4, 8, num);
	DelayUS(1); // need after Init

	// dma_Rx
	exFlash_dma_control->DMA_CONFIG = 1;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_SADDR = (uint32_t)&SSIx->SSI_DR;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_DADDR = (uint32_t)buff;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL = DIEC | SNOCHG | P2M_DMA | DBSIZE_1 | SBSIZE_1 | DWIDTH_B | SWIDTH_B; // 1x8 bits
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL_HIGH = num;															// max_len: 0x0FFF
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG = (HS_SEL_SRC_HARD) | (HS_SEL_DST_SOFT);								//|(CH_PRIOR1);
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG_HIGH = (exFlash_SRC_RX);

	exFlash_dma_control->DMA_CHEN |= CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM); // enable dma channel
	// printf("1m_dma_control->DMA_RAWTFR: 0x%08x\r\n", exFlash_dma_control->DMA_RAWTFR);

#if 1
	SSIx->SSI_DR = W25N01GV_READ_FAST_DUAL;
	SSIx->SSI_DR = column; // column
#else
	//SSIx->SSI_DR=((0x1300<<16)|(page));
	//SSIx->SSI_DR=((0x3b<<16)|(column));
#endif

	SSIx->SSI_DMACR = DMACR_RDMAE;
	DelayUS(1); // need after Init
//----------------- END -----------------------------------------------------
#else
//-----------------QUAL MODE-----------------------------------------------------
//----------------- END -----------------------------------------------------
#endif
}

void NandFlash_DMA_Wait(void)
{
	SSI_TypeDef *SSIx = SSI1;
	//	while((exFlash_dma_control->DMA_RAWTFR & CHANNEL_STAT(0)) != CHANNEL_STAT(0));
	//	exFlash_dma_control->DMA_CLRTFR =  exFlash_dma_control->DMA_STATTFR;
	while (exFlash_dma_control->DMA_CHEN & CHANNEL_STAT(exFlash_CHNUM))
	{
		;
	}
	exFlash_dma_control->DMA_CHEN &= ~(CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));
	//	exFlash_dma_control->DMA_CONFIG = 0;                 //Do not disable
	SSIx->SSI_DMACR = 0;
}
